package com.xiyuan.smartutils;

import com.xiyuan.smartutils.constants.SignConstants;
import com.xiyuan.smartutils.extend.KV;

import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;

import static com.xiyuan.smartutils.DateTimes.*;

/**
 * SQL相关工具类
 *
 * @version v1.0.0 @author lgz 2018-8-27 新建与整理
 */

@SuppressWarnings("all")
public final class Sqls implements SignConstants {
    
    private Sqls() {}
    
    /**
     * sql格式化 成？的前缀字符 '#{' 例如： where id = #{keyName}， 格式化后，#{keyName} 为 where id =？
     */
    public static final String SQL_PREPARED_PREFIX = "#{";
    /**
     * sql格式化 成？的后缀字符 '#{' 例如： where id = #{keyName}， 格式化后，#{keyName} 为 where id =？
     */
    public static final char SQL_PREPARED_SUFFIX = '}';
    
    /*************************************************************************************/
    // 字符串格式化
    /*************************************************************************************/
    
    /**
     * 格式化SQL语句中值的单引号为两个单引号
     *
     * @param sql 有单引号的SQL语句中的值
     * @return 替换成两个单引号
     */
    public static String formatQuote(String sql) {
        return sql.replaceAll("'", "''");
    }
    
    /**
     * 格式化SQL语句，对\r&\n&\t作一个空格处理，然后多个格式替换成单空格
     *
     * @param sql SQL语句
     * @return 格式化的SQL语句
     */
    public static String formatSpace(String sql) {
        sql = sql.replaceAll("\n", _ONE_SPACE_);
        sql = sql.replaceAll("\r", _ONE_SPACE_);
        sql = sql.replaceAll("\t", _ONE_SPACE_);
        
        return StrUtils.formatSpace(sql);
    }
    
    /**
     * 替换SQL中${key}可替换的值
     *
     * @param sql        SQL语句
     * @param replaceMap 可替换的表
     * @return 新的SQL语句
     */
    public static String formatReplaceMap(String sql, Map<String, String> replaceMap) {
        if (replaceMap == null || replaceMap.isEmpty()) {
            return sql;
        }
        
        StringBuilder strb = new StringBuilder();
        StringBuilder paramKey = new StringBuilder();
        StringBuilder pre = new StringBuilder();
        boolean open = false, preOpen = false;
        for (int i = 0; i < sql.length(); i++) {
            char c = sql.charAt(i);
            if (!open && !preOpen && c == '$') {
                pre.append(c);
                preOpen = true;
                continue;
            }
            
            if (!open && preOpen && Validates.isWhitespace(c)) {
                pre.append(c);
                continue;
            }
            
            if (!open && preOpen) {// '$' 开始
                if (c == '{' || Validates.isWhitespace(c)) {
                    open = true;
                    pre.append(c);
                }
                else if (c == '$') {
                    strb.append(pre);
                    pre.setLength(0);
                    pre.append(c);
                }
                else {
                    strb.append(pre).append(c);
                    pre.setLength(0);
                    preOpen = false;
                }
                
                continue;
            }
            
            if (open) {
                if (c == '}') {// 参数结束
                    open = false;
                    preOpen = false;
                    strb.append(replaceMap.get(StrUtils.trim(paramKey.toString()))).append(" ");
                    pre.setLength(0);
                    paramKey.setLength(0);
                    continue;
                }
                else if (Character.isJavaIdentifierPart(c) || Validates.isWhitespace(c)) {
                    // java 命名规则
                    paramKey.append(c);
                    continue;
                }
                else {// 非java 规则
                    strb.append(pre).append(paramKey);
                    pre.setLength(0);
                    paramKey.setLength(0);
                    open = false;
                    preOpen = false;
                }
            }
            
            if (!open && !preOpen) {
                strb.append(c);
            }
        }
        
        if (pre.length() > 0) {
            strb.append(pre);
        }
        if (paramKey.length() > 0) {
            strb.append(paramKey);
        }
        
        return strb.toString();
    }
    
    /**
     * 替换SQL中#{key} 编译成?可替换的值
     *
     * @param sql      SQL语句
     * @param paramMap 参数表
     * @return KV.key() = 新的SQL语句,KV.value() SQL对应的参数列表
     */
    public static KV<String, Object[]> toPreparedSQL(String sql, Map<String, Object> paramMap) {
        if (paramMap == null) {
            paramMap = new HashMap<String, Object>(0);
        }
        if (sql == null) {
            sql = "";
        }
        
        KV<String, Object[]> kv = new KV<String, Object[]>();
        ArrayList<Object> ls = new ArrayList<Object>(paramMap.size());
        StringBuilder strb = new StringBuilder();
        StringBuilder paramKey = new StringBuilder();
        StringBuilder pre = new StringBuilder();
        boolean open = false, preOpen = false;
        for (int i = 0; i < sql.length(); i++) {
            char c = sql.charAt(i);
            if (!open && !preOpen && c == SQL_PREPARED_PREFIX.charAt(0)) {
                pre.append(c);
                preOpen = true;
                continue;
            }
            
            if (!open && preOpen && Validates.isWhitespace(c)) {
                pre.append(c);
                continue;
            }
            
            if (!open && preOpen) {// '#' 开始
                if (c == SQL_PREPARED_PREFIX.charAt(1) || Validates.isWhitespace(c)) {
                    open = true;
                    pre.append(c);
                }
                else if (c == '#') {
                    strb.append(pre);
                    pre.setLength(0);
                    pre.append(c);
                }
                else {
                    strb.append(pre).append(c);
                    pre.setLength(0);
                    preOpen = false;
                }
                
                continue;
            }
            
            if (open) {
                if (c == SQL_PREPARED_SUFFIX) {// 参数结束
                    open = false;
                    preOpen = false;
                    strb.append("? ");
                    String key = StrUtils.trim(paramKey.toString());
                    pre.setLength(0);
                    paramKey.setLength(0);
                    ls.add(paramMap.get(key));// 添加参数
                    continue;
                }
                else if (Character.isJavaIdentifierPart(c) || Validates.isWhitespace(c)) {
                    // java 命名规则
                    paramKey.append(c);
                    continue;
                }
                else {// 非java 规则
                    strb.append(pre).append(paramKey);
                    pre.setLength(0);
                    paramKey.setLength(0);
                    open = false;
                    preOpen = false;
                }
            }
            
            if (!open && !preOpen) {
                strb.append(c);
            }
        }
        
        if (pre.length() > 0) {
            strb.append(pre);
        }
        if (paramKey.length() > 0) {
            strb.append(paramKey);
        }
        
        return kv.key(strb.toString()).value(ls.toArray());
    }
    /*************************************************************************************/
    // Timestamp & Date & Time转换为字符串
    /*************************************************************************************/
    
    /**
     * 生成标准的yyyy-MM-dd HH:mm:ss
     *
     * @param timestamp 数据库时间类型
     * @return 转化为字符串
     */
    public static String toDateTimeString(Timestamp timestamp) {
        if (timestamp == null) {
            return null;
        }
        
        return new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS).format(timestamp);
    }
    
    /**
     * 生成标准的yyyy-MM-dd
     *
     * @param timestamp 数据库时间类型
     * @return 字符串格式yyyy-MM-dd
     */
    public static String toDateString(Timestamp timestamp) {
        if (timestamp == null) {
            return null;
        }
        
        return new SimpleDateFormat(YYYY_MM_DD).format(timestamp);
    }
    
    /**
     * 生成标准的yyyy-MM-dd
     *
     * @param date 日期格式
     * @return 字符串格式yyyy-MM-dd
     */
    public static String toDateString(Date date) {
        if (date == null) {
            return null;
        }
        
        return new SimpleDateFormat("yyyy-MM-dd").format(date);
    }
    
    /**
     * 生成标准的HH:mm:ss
     *
     * @param timestamp 时间
     * @return 字符串格式HH:mm:ss
     */
    public static String toTimeString(Timestamp timestamp) {
        if (timestamp == null) {
            return null;
        }
        
        return new SimpleDateFormat(HH_MM_SS).format(timestamp);
    }
    
    /**
     * 生成标准的HH:mm:ss
     *
     * @param time 时间
     * @return 字符串格式HH:mm:ss
     */
    public static String toTimeString(Time time) {
        if (time == null) {
            return null;
        }
        
        return new SimpleDateFormat(HH_MM_SS).format(time);
    }
    
    /*************************************************************************************/
    // String转换为Date类型
    /*************************************************************************************/
    
    /**
     * String到java.sql.Date的转换 标准格式:yyyy-MM-dd
     *
     * @param date 日期字符串
     * @return java.sql.Date
     */
    public static Date toDate(String date) {
        return Date.valueOf(date);
    }
    
    /**
     * 生成java.sql.Date,通过传入year, month, day
     *
     * @param year  年
     * @param month 月
     * @param day   日
     * @return java.sql.Date
     */
    public static Date toDate(String year, String month, String day) {
        return Date.valueOf(year + "-" + month + "-" + day);
    }
    
    /**
     * 生成java.sql.Date,通过传入year, month, day
     *
     * @param year  年
     * @param month 月
     * @param day   日
     * @return java.sql.Date
     */
    public static Date toDate(int year, int month, int day) {
        return Date.valueOf(year + "-" + month + "-" + day);
    }
    
    /*************************************************************************************/
    // String转换为Time类型
    /*************************************************************************************/
    
    /**
     * 转换String 到 Time,格式:"HH:mm:ss"
     *
     * @param time 时间字符串
     * @return Time
     */
    public static Time toTime(String time) {
        return Time.valueOf(time);
    }
    
    /**
     * 生成 Time 通过输入时,分,秒
     *
     * @param hour   时 0-23
     * @param minute 分 0-59
     * @param second 秒 0-59
     * @return Time
     */
    public static Time toTime(String hour, String minute, String second) {
        return Time.valueOf(hour + ":" + minute + ":" + second);
    }
    
    /**
     * 生成 Time 通过输入时,分,秒
     *
     * @param hour   时 0-23
     * @param minute 分 0-59
     * @param second 秒 0-59
     * @return Time
     */
    public static Time toTime(int hour, int minute, int second) {
        return Time.valueOf(hour + ":" + minute + ":" + second);
    }
    
    /*************************************************************************************/
    // String转换为Timestamp类型
    /*************************************************************************************/
    
    /**
     * 返回当前时间
     */
    public static Timestamp nowTimestamp() {
        return new Timestamp(System.currentTimeMillis());
    }
    
    /**
     * 转换String 到 Timestamp,格式:"yyyy-MM-dd HH:mm:ss"
     *
     * @param datetime 格式:"yyyy-MM-dd HH:mm:ss"
     * @return Timestamp
     */
    public static Timestamp toTimestamp(String datetime) {
        return new Timestamp(DateTimes.toCalendar(datetime).getTimeInMillis());
    }
    
    /**
     * 转换String 到 Timestamp,格式:"yyyy-MM-dd"和"HH:mm:ss"
     *
     * @param date 日期格式: yyyy-MM-dd
     * @param time 时间格式: HH:mm:ss
     * @return Timestamp
     */
    public static Timestamp toTimestamp(String date, String time) {
        return new Timestamp(DateTimes.toCalendar(date, time).getTimeInMillis());
    }
    
    /**
     * 生成 Timestamp 通过输入年,月,日,时,分,秒
     *
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   时
     * @param minute 分
     * @param second 秒
     * @return Timestamp
     */
    public static Timestamp toTimestamp(String year,
                                        String month,
                                        String day,
                                        String hour,
                                        String minute,
                                        String second) {
        return new Timestamp(DateTimes.toCalendar(year, month, day, hour, minute, second).getTimeInMillis());
    }
    
    /**
     * 生成 Timestamp 通过输入年,月,日,时,分,秒
     *
     * @param year   年
     * @param month  月
     * @param day    日
     * @param hour   时
     * @param minute 分
     * @param second 秒
     * @return Timestamp
     */
    public static Timestamp toTimestamp(int year, int month, int day, int hour, int minute, int second) {
        if (year < 1000) {
            year = 1000 + year;
        }
        return new Timestamp(DateTimes.toCalendar(year, month, day, hour, minute, second).getTimeInMillis());
    }
    
    /*************************************************************************************/
    // 取指定日期的00:00:00和23:59:59
    /*************************************************************************************/
    
    /**
     * 转换日期到Timestamp，时间固定为00:00:00
     *
     * @param date 日期格式: yyyy-MM-dd
     * @return Timestamp
     */
    public static Timestamp toTimestampBegin(String date) {
        return toTimestamp(date, "00:00:00");
    }
    
    /**
     * 转换日期到Timestamp，时间固定为23:59:59
     *
     * @param date 日期格式: yyyy-MM-dd
     * @return Timestamp
     */
    public static Timestamp toTimestampEnd(String date) {
        return toTimestamp(date, "23:59:59");
    }
    
    /**
     * 返回从当日00:00:00的时间
     */
    public static Timestamp toTimestampBegin(Timestamp stamp) {
        Calendar calendar = Calendar.getInstance();
        // 指到指定的时间
        calendar.setTime(stamp);
        // 设置零点零分零秒
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        
        return new Timestamp(calendar.getTimeInMillis());
    }
    
    /**
     * 返回从当日结束的23:59:59时间
     */
    public static Timestamp toTimestampEnd(Timestamp stamp) {
        Calendar calendar = Calendar.getInstance();
        // 指到指定的时间
        calendar.setTime(stamp);
        // 设置23:59:59
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        calendar.set(Calendar.MILLISECOND, 0);
        
        return new Timestamp(calendar.getTimeInMillis());
    }
    
    /*************************************************************************************/
    // 取指定时间的向前（day取值为负数）和向后的时间格式
    /*************************************************************************************/
    
    /**
     * 返回下一天零点零分零秒的时间
     */
    public static Timestamp getNextTimestampBegin(Timestamp stamp) {
        return getNextTimestampBegin(stamp, 1);
    }
    
    /**
     * 返回多少天后零点零分零秒的时间
     */
    public static Timestamp getNextTimestampBegin(Timestamp stamp, int day) {
        Calendar calendar = Calendar.getInstance();
        // 指到指定的时间
        calendar.setTime(stamp);
        // 设置零点零分零秒
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        // 向后移天数
        calendar.add(Calendar.DAY_OF_MONTH, day);
        
        return new Timestamp(calendar.getTimeInMillis());
    }
    
    /**
     * 返回从多少日后结束的23:59:59时间
     */
    public static Timestamp getNextTimestampEnd(Timestamp stamp) {
        return getNextTimestampEnd(stamp, 1);
    }
    
    /**
     * 返回从多少日后结束的23:59:59时间
     */
    public static Timestamp getNextTimestampEnd(Timestamp stamp, int day) {
        Calendar calendar = Calendar.getInstance();
        // 指到指定的时间
        calendar.setTime(stamp);
        // 设置23:59:59
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        calendar.set(Calendar.MILLISECOND, 0);
        // 向后移天数
        calendar.add(Calendar.DAY_OF_MONTH, day);
        
        return new Timestamp(calendar.getTimeInMillis());
    }
    
    /*************************************************************************************/
    // 初始化脚本处理
    /*************************************************************************************/
    
    /**
     * 把初始化脚本，生成一行一个SQL语句
     *
     * @param sqls SQL文本
     * @return SQL列表
     */
    public static List<String> toSqlList(String sqls) {
        if (Validates.isBlank(sqls)) {
            return new ArrayList<String>();
        }
        
        List<String> sqlList = new ArrayList<String>();
        
        String[] sqlArr = Arrays.toStringArray(sqls, "\n");
        for (String sql : sqlArr) {
            sql = StrUtils.trim(sql);
            if (Validates.isEmpty(sql) || StrUtils.startsWith(sql, "--") || StrUtils.startsWith(sql, "commit")) {
                continue;
            }
            
            sql = StrUtils.trimRight(sql, ";");
            sqlList.add(sql);
        }
        
        return sqlList;
    }
}
